function mxOutline(graph, container)
{
	this.source = graph;
	this.graph = new mxGraph(container, graph.getModel(), this.graphRenderHint);

	if (mxClient.IS_SVG)
	{
		var node = this.graph.getView().getCanvas().parentNode;
		node.setAttribute('shape-rendering', 'optimizeSpeed');
		node.setAttribute('image-rendering', 'optimizeSpeed');
	}
	this.graph.setStylesheet(graph.getStylesheet());
	this.graph.setEnabled(false);
	this.graph.labelsVisible = false;
	var self = this;
	graph.getModel().addListener(mxEvent.CHANGE, function(sender, evt)
	{
		self.update();
	});
	this.graph.addMouseListener(this);
	var self = this;
	var funct = function(sender)
	{
		self.update();
	};
	this.source.getModel().addListener(mxEvent.CHANGE, funct);
	var view = this.source.getView();
	view.addListener(mxEvent.SCALE, funct);
	view.addListener(mxEvent.TRANSLATE, funct);
	view.addListener(mxEvent.SCALE_AND_TRANSLATE, funct);
	view.addListener(mxEvent.DOWN, funct);
	view.addListener(mxEvent.UP, funct);
	graph.addListener(mxEvent.REFRESH, function(sender)
	{
		self.graph.setStylesheet(graph.getStylesheet());
		self.graph.refresh();
	});
	this.bounds = new mxRectangle(0, 0, 0, 0);
	this.selectionBorder = new mxRectangleShape(this.bounds, null, mxConstants.OUTLINE_COLOR,
		mxConstants.OUTLINE_STROKEWIDTH);

	this.selectionBorder.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG)
		? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
	this.selectionBorder.init(this.graph.getView().getOverlayPane());
	var s = 3;
	this.sizer = new mxRectangleShape(this.bounds, mxConstants.OUTLINE_HANDLE_FILLCOLOR,
		mxConstants.OUTLINE_HANDLE_STROKECOLOR);
	this.sizer.dialect = this.graph.dialect;
	this.sizer.init(this.graph.getView().getOverlayPane());

	if (this.enabled)
	{
		this.sizer.node.style.cursor = 'pointer';
	}
	mxEvent.addListener(this.sizer.node, 'mousedown', function(evt)
	{
		self.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, null, 0));
	});
	this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';
	this.sizer.node.style.display = this.selectionBorder.node.style.display;
	this.refresh();
};
mxOutline.prototype.graphRenderHint = mxConstants.RENDERING_HINT_FASTER;
mxOutline.prototype.enabled = true;
mxOutline.prototype.showViewport = true;
mxOutline.prototype.isEnabled = function()
{
	return this.enabled;
};
mxOutline.prototype.setEnabled = function(enabled)
{
	this.enabled = enabled;
};
mxOutline.prototype.refresh = function(revalidate)
{
	this.update();
	this.graph.refresh();
};
mxOutline.prototype.update = function(revalidate)
{
	var bounds = this.source.getGraphBounds();
	var mw = parseInt(this.source.container.clientWidth);
	var mh = parseInt(this.source.container.clientHeight);
	var c = this.graph.container;
	var cw = parseInt(c.clientWidth);
	var ch = parseInt(c.clientHeight);

	if (cw > 0 || ch > 0)
	{
		var w = Math.max(mw, bounds.width + Math.abs(bounds.x)) + cw * 0.1;
		var h = Math.max(mh, bounds.height + Math.abs(bounds.y)) + ch * 0.1;
		var scale = Math.min(cw / w, ch / h);

		if (this.graph.getView().scale != scale)
		{
			this.graph.getView().scale = scale
			revalidate = true;
		}
	}
	var navView = this.graph.getView();

	if (navView.currentRoot != this.source.getView().currentRoot)
	{
		navView.setCurrentRoot(this.source.getView().currentRoot);
	}
	var t = this.source.view.translate;
	var tx = Math.max(0, t.x);
	var ty = Math.max(0, t.y);

	if (navView.translate.x != tx || navView.translate.y != ty)
	{
		navView.translate.x = tx;
		navView.translate.y = ty;
		revalidate = true;
	}
	var t2 = navView.translate;
	var scale = this.source.getView().scale;
	var scale2 = scale / navView.scale;
	var scale3 = 1.0 / navView.scale;
	var container = this.source.container;
	this.bounds = new mxRectangle((t2.x - t.x) / scale3, (t2.y - t.y)
		/ scale3, (container.clientWidth / scale2), (container.clientHeight / scale2));
	this.bounds.x += this.source.container.scrollLeft * navView.scale / scale;
	this.bounds.y += this.source.container.scrollTop * navView.scale / scale;
	this.selectionBorder.bounds = this.bounds;
	this.selectionBorder.redraw();
	var s = 3;
	this.sizer.bounds = new mxRectangle(this.bounds.x + this.bounds.width - s, this.bounds.y + this.bounds.height - s,
		2 * s, 2 * s);
	this.sizer.redraw();

	if (revalidate)
	{
		this.graph.view.revalidate();
	}
};
mxOutline.prototype.mouseDown = function(sender, me)
{
	if (this.enabled && this.showViewport)
	{
		this.index = me.getHandle();
		this.startX = me.getX();
		this.startY = me.getY();
		this.active = true;

		if (mxUtils.hasScrollbars(this.source.container))
		{
			this.dx0 = this.source.container.scrollLeft;
			this.dy0 = this.source.container.scrollTop;
		}
		else
		{
			this.dx0 = 0;
			this.dy0 = 0;
		}
	}
	me.consume();
};
mxOutline.prototype.mouseMove = function(sender, me)
{
	if (this.active)
	{
		this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';
		this.sizer.node.style.display = this.selectionBorder.node.style.display;
		var dx = me.getX() - this.startX;
		var dy = me.getY() - this.startY;
		var bounds = null;

		if (this.index == null)
		{
			var scale = this.graph.getView().scale;
			bounds = new mxRectangle(this.bounds.x + dx, this.bounds.y + dy, this.bounds.width, this.bounds.height);
			this.selectionBorder.bounds = bounds;
			this.selectionBorder.redraw();
			dx /= scale;
			dx *= this.source.getView().scale;
			dy /= scale;
			dy *= this.source.getView().scale;
			this.source.panGraph(-dx - this.dx0, -dy - this.dy0);
		}
		else
		{
			var container = this.source.container;
			var viewRatio = container.clientWidth / container.clientHeight;
			dy = dx / viewRatio;
			bounds = new mxRectangle(this.bounds.x, this.bounds.y, this.bounds.width + dx, this.bounds.height + dy);
			this.selectionBorder.bounds = bounds;
			this.selectionBorder.redraw();
		}
		var s = 3;
		this.sizer.bounds = new mxRectangle(bounds.x + bounds.width - s, bounds.y + bounds.height - s, 2 * s, 2 * s);
		this.sizer.redraw();
		me.consume();
	}
};
mxOutline.prototype.mouseUp = function(sender, me)
{
	if (this.active)
	{
		var dx = me.getX() - this.startX;
		var dy = me.getY() - this.startY;

		if (Math.abs(dx) > 0 || Math.abs(dy) > 0)
		{
			if (this.index == null)
			{
				if (!mxUtils.hasScrollbars(this.source.container))
				{
					this.source.panGraph(0, 0);
					dx /= this.graph.getView().scale;
					dy /= this.graph.getView().scale;
					var t = this.source.getView().translate;
					this.source.getView().setTranslate(t.x - dx, t.y - dy);
				}
			}
			else
			{
				var w = this.selectionBorder.bounds.width;
				var h = this.selectionBorder.bounds.height;
				var scale = this.source.getView().scale;
				this.source.getView().setScale(scale - (dx * scale) / w);
			}
			this.update();
			me.consume();
		}
		this.index = null;
		this.active = false;
	}
};